package com.baijiayun.liveuibase.devicetesting.fragment

import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.*
import android.hardware.camera2.*
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.util.Size
import android.view.*
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.baijiayun.livecore.utils.LPRxUtils
import com.baijiayun.liveuibase.R
import com.baijiayun.liveuibase.devicetesting.DeviceTestingViewModel
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.bjy_base_fragment_device_testing_camera.*
import java.lang.Long
import java.util.*
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit

class DeviceTestingCameraFragment : Fragment() {
    private var disposableOfTime: Disposable? = null
    private lateinit var viewModel: DeviceTestingViewModel

    private var captureSession: CameraCaptureSession? = null
    private var cameraDevice: CameraDevice? = null
    private var backgroundThread: HandlerThread? = null
    private var backgroundHandler: Handler? = null
    private lateinit var previewRequestBuilder: CaptureRequest.Builder
    private lateinit var previewRequest: CaptureRequest
    private var flashSupported = false
    private val cameraOpenCloseLock = Semaphore(1)
    private var mCameraId = "1"
    private var sensorOrientation = 0
    private lateinit var previewSize: Size
    private lateinit var textureView: TextureView
    private val MAX_PREVIEW_WIDTH = 1920
    private val MAX_PREVIEW_HEIGHT = 1080
    private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
        @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
        override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) {
            openCamera(width, height, mCameraId)
        }

        @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
        override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) {
            configureTransform(width, height)
        }

        override fun onSurfaceTextureDestroyed(texture: SurfaceTexture) = true

        override fun onSurfaceTextureUpdated(texture: SurfaceTexture) = Unit
    }

    private val stateCallback = @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    object : CameraDevice.StateCallback() {
        override fun onOpened(cameraDevice: CameraDevice) {
            cameraOpenCloseLock.release()
            this@DeviceTestingCameraFragment.cameraDevice = cameraDevice
            createCameraPreviewSession()
        }

        override fun onDisconnected(cameraDevice: CameraDevice) {
            cameraOpenCloseLock.release()
            cameraDevice.close()
            showCameraError()
            this@DeviceTestingCameraFragment.cameraDevice = null
        }

        override fun onError(cameraDevice: CameraDevice, error: Int) {
            onDisconnected(cameraDevice)
            showCameraError()
        }

    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.bjy_base_fragment_device_testing_camera, container, false)
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProvider(requireActivity()).get(DeviceTestingViewModel::class.java)
        textureView = bjy_base_fragment_device_testing_camera_preview
        initView()
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun initView() {
        bjy_base_fragment_device_testing_camera_current.setOnClickListener {
            if (bjy_base_fragment_device_testing_camera_another.visibility == View.GONE) {
                bjy_base_fragment_device_testing_camera_name_container.background = ContextCompat.getDrawable(context!!, R.drawable.bjy_base_bg_device_testing_camera_select)
                bjy_base_fragment_device_testing_camera_current.background = ContextCompat.getDrawable(context!!, R.drawable.bjy_base_bg_device_testing_fragment_select_tv_selected)
                bjy_base_fragment_device_testing_camera_drop_down_iv.setImageResource(R.drawable.bjy_base_icon_loading_device_check_pass)
                bjy_base_fragment_device_testing_camera_another.visibility = View.VISIBLE
            } else {
                bjy_base_fragment_device_testing_camera_name_container.setBackgroundColor(Color.WHITE)
                bjy_base_fragment_device_testing_camera_drop_down_iv.setImageResource(R.drawable.biy_base_device_testing_icon_drop_down)
                bjy_base_fragment_device_testing_camera_current.background = ContextCompat.getDrawable(context!!, R.drawable.bjy_base_bg_device_testing_fragment_select_tv_normal)
                bjy_base_fragment_device_testing_camera_another.visibility = View.GONE
            }
        }
        bjy_base_fragment_device_testing_camera_another.setOnClickListener {
            mCameraId = if (mCameraId == "0") "1" else "0"
            bjy_base_fragment_device_testing_camera_current.text = if (mCameraId == "0") getText(R.string.bjy_base_device_testing_camera_back) else getText(R.string.bjy_base_device_testing_camera_front)
            bjy_base_fragment_device_testing_camera_another.text = if (mCameraId == "0") getText(R.string.bjy_base_device_testing_camera_front) else getText(R.string.bjy_base_device_testing_camera_back)
            bjy_base_fragment_device_testing_camera_another.visibility = View.GONE
            bjy_base_fragment_device_testing_camera_name_container.setBackgroundColor(Color.WHITE)
            bjy_base_fragment_device_testing_camera_drop_down_iv.setImageResource(R.drawable.biy_base_device_testing_icon_drop_down)
            bjy_base_fragment_device_testing_camera_current.background = ContextCompat.getDrawable(context!!, R.drawable.bjy_base_bg_device_testing_fragment_select_tv_normal)
            closeCamera()
            startBackgroundThread()
            if (textureView.isAvailable) {
                openCamera(textureView.width, textureView.height, mCameraId)
            } else {
                textureView.surfaceTextureListener = surfaceTextureListener
            }
        }
        bjy_base_fragment_device_testing_camera_description_tv.visibility = View.GONE
        bjy_base_fragment_device_testing_camera_positive_btn.visibility = View.GONE
        bjy_base_fragment_device_testing_camera_negative_btn.visibility = View.GONE
        bjy_base_fragment_device_testing_camera_negative_btn.setOnClickListener {
            val dialogFragment = DeviceTestingDialogFragment()
            dialogFragment.setTitle(getString(R.string.bjy_base_device_testing_camera_no_look_as_question))
            dialogFragment.setContent(getString(R.string.bjy_base_device_testing_camera_confirm_question))
            dialogFragment.setNegativeButtonText(getString(R.string.bjy_base_device_testing_camera_no_look))
            dialogFragment.setPositiveButtonText(getString(R.string.bjy_base_device_testing_camera_can_look))
            dialogFragment.setButtonClickedListener(object : DeviceTestingDialogFragment.OnButtonClickedListener {
                override fun onPositive() {
                    dialogFragment.dismissAllowingStateLoss()
                    viewModel.cameraResult = true
                    viewModel.setTestStep(DeviceTestingViewModel.TestStep.TestSpeaker)
                }

                override fun onNegative() {
                    dialogFragment.dismissAllowingStateLoss()
                    viewModel.cameraResult = false
                    viewModel.setTestStep(DeviceTestingViewModel.TestStep.TestSpeaker)
                }
            })
            dialogFragment.show(activity!!.supportFragmentManager, "camera")
        }
        bjy_base_fragment_device_testing_camera_positive_btn.setOnClickListener {
            viewModel.cameraResult = true
            viewModel.setTestStep(DeviceTestingViewModel.TestStep.TestSpeaker)
        }
        LPRxUtils.dispose(disposableOfTime)
        disposableOfTime = Observable.timer(2, TimeUnit.SECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe{
                    bjy_base_fragment_device_testing_camera_description_tv.visibility = View.VISIBLE
                    bjy_base_fragment_device_testing_camera_positive_btn.visibility = View.VISIBLE
                    bjy_base_fragment_device_testing_camera_negative_btn.visibility = View.VISIBLE
                }
    }

    private fun showCameraError() {
        bjy_base_fragment_device_testing_camera_not_connect.visibility = View.VISIBLE
        bjy_base_fragment_device_testing_camera_negative_btn.apply {
            this.background = ContextCompat.getDrawable(context!!, R.drawable.bjy_base_fragment_device_testing_positive_button)
            this.isEnabled = true
            this.setTextColor(ContextCompat.getColor(context!!, R.color.base_white))
            this.setOnClickListener {
                viewModel.cameraResult = false
                viewModel.setTestStep(DeviceTestingViewModel.TestStep.TestSpeaker)
            }
        }
        bjy_base_fragment_device_testing_camera_positive_btn.apply {
            this.background = ContextCompat.getDrawable(context!!, R.drawable.bjy_base_fragment_device_testing_positive_button)
            this.isEnabled = false
            this.setTextColor(ContextCompat.getColor(context!!, R.color.bjy_base_window_loading_tip_text_color))
        }
        bjy_base_fragment_device_testing_camera_description_tv.apply {
            this.visibility = View.VISIBLE
            this.text = getText(R.string.bjy_base_device_testing_camera_not_found)
            this.setTextColor(ContextCompat.getColor(context!!, R.color.bjy_base_window_loading_device_check_failed_text_color))
        }
        bjy_base_fragment_device_testing_camera_current.isEnabled = false
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    @SuppressLint("MissingPermission")
    private fun openCamera(width: Int, height: Int, cameraId: String) {
        val permission = ContextCompat.checkSelfPermission(activity!!, Manifest.permission.CAMERA)
        if (permission != PackageManager.PERMISSION_GRANTED) {
            showCameraError()
            return
        }
        mCameraId = cameraId
        setUpCameraOutputs(width, height)
        configureTransform(width, height)
        val manager = activity!!.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        try {
            // Wait for camera to open - 2.5 seconds is sufficient
            if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw RuntimeException("Time out waiting to lock camera opening.")
            }
            manager.openCamera(cameraId, stateCallback, backgroundHandler)
        } catch (e: CameraAccessException) {
            showCameraError()
        } catch (e: InterruptedException) {
            showCameraError()
        }

    }

    private fun closeCamera() {
        try {
            cameraOpenCloseLock.acquire()
            captureSession?.close()
            captureSession = null
            cameraDevice?.close()
            cameraDevice = null
        } catch (e: InterruptedException) {
            throw RuntimeException("Interrupted while trying to lock camera closing.", e)
        } finally {
            cameraOpenCloseLock.release()
        }
    }


    private fun startBackgroundThread() {
        backgroundThread = HandlerThread("CameraBackground").also { it.start() }
        backgroundHandler = Handler(backgroundThread?.looper)
    }

    private fun stopBackgroundThread() {
        backgroundThread?.quitSafely()
        try {
            backgroundThread?.join()
            backgroundThread = null
            backgroundHandler = null
        } catch (e: InterruptedException) {
            showCameraError()
        }

    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun createCameraPreviewSession() {
        try {
            val texture = textureView.surfaceTexture
            val surface = Surface(texture)
            previewRequestBuilder = cameraDevice!!.createCaptureRequest(
                    CameraDevice.TEMPLATE_PREVIEW
            )
            previewRequestBuilder.addTarget(surface)
            cameraDevice?.createCaptureSession(listOf(surface),
                    object : CameraCaptureSession.StateCallback() {
                        override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
                            if (cameraDevice == null) return
                            captureSession = cameraCaptureSession
                            try {
                                previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
                                setAutoFlash(previewRequestBuilder)
                                previewRequest = previewRequestBuilder.build()
                                captureSession?.setRepeatingRequest(previewRequest,
                                        null, backgroundHandler)
                            } catch (e: CameraAccessException) {
                                showCameraError()
                            }
                        }

                        override fun onConfigureFailed(session: CameraCaptureSession) {
                            showCameraError()
                        }
                    }, null)
        } catch (e: CameraAccessException) {
            showCameraError()
        }

    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun setUpCameraOutputs(width: Int, height: Int) {
        val manager = activity!!.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        try {
            val characteristics = manager.getCameraCharacteristics(mCameraId)
            val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
            val largest = Collections.max(
                    listOf(*map!!.getOutputSizes(ImageFormat.JPEG)),
                    CompareSizesByArea())
            val displayRotation = activity!!.windowManager.defaultDisplay.rotation
            sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
            val swappedDimensions = areDimensionsSwapped(displayRotation)
            val displaySize = Point()
            activity!!.windowManager.defaultDisplay.getSize(displaySize)
            val rotatedPreviewWidth = if (swappedDimensions) height else width
            val rotatedPreviewHeight = if (swappedDimensions) width else height
            var maxPreviewWidth = if (swappedDimensions) displaySize.y else displaySize.x
            var maxPreviewHeight = if (swappedDimensions) displaySize.x else displaySize.y

            if (maxPreviewWidth > MAX_PREVIEW_WIDTH) maxPreviewWidth = MAX_PREVIEW_WIDTH
            if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) maxPreviewHeight = MAX_PREVIEW_HEIGHT
            previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture::class.java),
                    rotatedPreviewWidth, rotatedPreviewHeight,
                    maxPreviewWidth, maxPreviewHeight,
                    largest)
            flashSupported =
                    characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true

        } catch (e: CameraAccessException) {
            showCameraError()
        } catch (e: NullPointerException) {
            showCameraError()
        }

    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun configureTransform(viewWidth: Int, viewHeight: Int) {
        activity ?: return
        val rotation = activity!!.windowManager.defaultDisplay.rotation
        val matrix = Matrix()
        val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
        val bufferRect = RectF(0f, 0f, previewSize.height.toFloat(), previewSize.width.toFloat())
        val centerX = viewRect.centerX()
        val centerY = viewRect.centerY()
        bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            matrix.postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
        } else if (Surface.ROTATION_180 == rotation) {
            matrix.postRotate(180f, centerX, centerY)
        }
        textureView.setTransform(matrix)
    }

    private fun areDimensionsSwapped(displayRotation: Int): Boolean {
        var swappedDimensions = false
        when (displayRotation) {
            Surface.ROTATION_0, Surface.ROTATION_180 -> {
                if (sensorOrientation == 90 || sensorOrientation == 270) {
                    swappedDimensions = true
                }
            }
            Surface.ROTATION_90, Surface.ROTATION_270 -> {
                if (sensorOrientation == 0 || sensorOrientation == 180) {
                    swappedDimensions = true
                }
            }
            else -> {
                // do nothing
            }
        }
        return swappedDimensions
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun setAutoFlash(requestBuilder: CaptureRequest.Builder) {
        if (flashSupported) {
            requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
        }
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun chooseOptimalSize(
            choices: Array<Size>,
            textureViewWidth: Int,
            textureViewHeight: Int,
            maxWidth: Int,
            maxHeight: Int,
            aspectRatio: Size
    ): Size {
        val bigEnough = ArrayList<Size>()
        val notBigEnough = ArrayList<Size>()
        val w = aspectRatio.width
        val h = aspectRatio.height
        for (option in choices) {
            if (option.width <= maxWidth && option.height <= maxHeight &&
                    option.height == option.width * h / w) {
                if (option.width >= textureViewWidth && option.height >= textureViewHeight) {
                    bigEnough.add(option)
                } else {
                    notBigEnough.add(option)
                }
            }
        }
        return when {
            bigEnough.size > 0 -> {
                Collections.min(bigEnough, CompareSizesByArea())
            }
            notBigEnough.size > 0 -> {
                Collections.max(notBigEnough, CompareSizesByArea())
            }
            else -> {
                choices[0]
            }
        }
    }


    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    override fun onResume() {
        super.onResume()
        startBackgroundThread()
        if (textureView.isAvailable) {
            openCamera(textureView.width, textureView.height, mCameraId)
        } else {
            textureView.surfaceTextureListener = surfaceTextureListener
        }
    }

    override fun onPause() {
        closeCamera()
        stopBackgroundThread()
        LPRxUtils.dispose(disposableOfTime)
        super.onPause()
    }

    internal class CompareSizesByArea : Comparator<Size> {
        @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
        override fun compare(lhs: Size, rhs: Size) =
                Long.signum(lhs.width.toLong() * lhs.height - rhs.width.toLong() * rhs.height)

    }
}